package edu.washington.apl.weather.packet;

import edu.washington.apl.weather.util.WeatherProperties;

import org.apache.log4j.Logger;

import java.util.HashMap;

public class PacketDecoder {

    protected static Logger logger = Logger.getLogger(Packet.class);
    private boolean[] packetsReady = new boolean[]{true, true, true, true, false, false};
    private boolean[] packetsAlways = new boolean[]{false, false, false, false, false, false};
    private static final int MILLIS_PER_HOUR = 60 * 60 * 1000;

    public PacketDecoder() {

        HashMap<Integer, Double> rateMap = new HashMap<Integer, Double>();
        rateMap.put(Packet.ANENOMETER_T, WeatherProperties.getWindRate());
        rateMap.put(Packet.INDOOR_T, WeatherProperties.getIndoorRate());
        rateMap.put(Packet.OUTDOOR_T, WeatherProperties.getOutdoorRate());
        rateMap.put(Packet.RAIN_T, WeatherProperties.getRainRate());

        for (int type : rateMap.keySet()) {
            double rate = rateMap.get(type);

            if (rate > 0) {
                new SleepTimer(type, getSleepInterval(rate)).start();
            } else if (rate == 0) {
                packetsAlways[type] = true;
            } else {
                packetsReady[type] = false;
            }
        }
    }

    // input: reads per hour
    // output: sleep duration in millis (millis per read)
    private static int getSleepInterval(double readsPerHour) {
        return (int) ((1.0 / readsPerHour) * MILLIS_PER_HOUR);
    }

    // strip off two leading 0xFF and make packet array correct length
    private static byte[] strip(byte[] packet, int length) {
        byte[] p = new byte[length - 2];
        System.arraycopy(packet, 2, p, 0, length - 2);
        return p;
    }

    // trim packet array
    private static byte[] trim(byte[] packet, int length) {
        byte[] p = new byte[length];
        System.arraycopy(packet, 0, p, 0, length);
        return p;
    }

    public Packet decode(byte[] packet) {
        if (packet == null) {
            return null;
        }
        return decode(packet, packet.length);
    }

    // packet should come in as valid checksummed packet
    public Packet decode(byte[] packet, int length) {
        if (packet == null || length <= 3 || ((packet[0] & 0xFF) != 0xFF) || ((packet[1] & 0xFF) != 0xFF)) {
            logger.error("packet format error");
            return null;
        }

        // make packet array actual length
        packet = trim(packet, length);

        switch (packet[2]) {
            case Packet.ANENOMETER:
                return new AnenometerPacket(packet);

            case Packet.RAIN:
                return new RainPacket(packet);
            case Packet.OUTDOOR_1:
            case Packet.OUTDOOR_2:
                return new OutdoorPacket(packet);

            case Packet.INDOOR_1:
            case Packet.INDOOR_2:
                return new IndoorPacket(packet);

            case Packet.SEQUENCE:
                return new SequencePacket(packet);

            case Packet.STATUS:
                return new StatusPacket(packet);

            default:
                logger.error("packet type error: " + packet[2]);
                return null;
        }

    }

    private synchronized void timerRing(int type) {
        packetsReady[type] = true;
    }

    // this could be implemented better 
    private class SleepTimer extends Thread {

        private int type = 0;
        private int millis = 0;

        public SleepTimer(int type, int millis) {
            this.type = type;
            this.millis = millis;
        }

        public void run() {
            try {
                while (true) {
                    this.sleep(millis);
                    timerRing(type);
                }
            } catch (Exception e) {
                logger.error("timer thread death from sleep error", e);
            }
        }
    }
}
